package cn.amossun.starter.mybatis.data.security.rewrite;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.amossun.starter.common.util.ClassUtil;
import cn.amossun.starter.mybatis.data.security.core.FieldConditionContext;
import cn.amossun.starter.mybatis.data.security.properties.ResultConfiguration;
import cn.amossun.starter.mybatis.data.security.properties.ResultMapConfiguration;
import cn.amossun.starter.mybatis.data.security.rule.MybatisDecryptRule;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
* @description: 解密重写预处理
* @author: Amos.Sun
* @DateTime: 2020/3/9 19:37
**/
@Slf4j
public class DecryptPreParameterRewriter {

    public DecryptPreParameterRewriter(MybatisDecryptRule mybatisDecryptRule) throws Exception {
        decryptRule = mybatisDecryptRule;
    }

    private final MybatisDecryptRule decryptRule;

    /**
     * 重写返回值
     * @param result
     * @throws IllegalAccessException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws SQLException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws UnsupportedEncodingException
     */
    public void rewrite(Object result) throws Exception {
        // 返回值为空
        if(result == null) {
            return;
        }
        // 判断如果为jdk基本类型|| 包装类型 || ORM拦截配置为空，则跳过处理
        Class clazz = result.getClass();
        if(ClassUtil.isPrimitive(clazz)) {
            return;
        }

        if(result instanceof Map || result instanceof Set) {
            return;
        }

        if(result instanceof List) {
            List resultList = (List) result;
            if(CollectionUtil.isEmpty(resultList)) {
                return;
            }
            decryptRule.refreshDecryptRuleMap();

            //判断集合第一个对象实例是否需要解密
            if(!checkListFirstObject(resultList.get(0))) {
                log.info("当前对象{}, 暂未配置解密处理规则.", resultList.get(0).getClass().getName());
                return;
            }
            Object parameterObject = resultList.get(0);
            FieldConditionContext context = new FieldConditionContext(parameterObject, decryptRule);
            if (CollectionUtil.isEmpty(context.getDecryptFields())) {
                return;
            }
            for(int i = 0; i < resultList.size(); i++) {
                parameterObject = resultList.get(i);
                if(parameterObject == null) {
                    continue;
                }
                decryptObject(parameterObject, context.getDecryptFields());
            }
        } else {
            decryptRule.refreshDecryptRuleMap();
            //判断当前对象实例是否需要解密
            if(!checkListFirstObject(result)) {
                log.info("当前对象{}, 暂未配置解密处理规则.", result.getClass().getName());
                return;
            }
            Object parameterObject = result;
            FieldConditionContext context = new FieldConditionContext(parameterObject, decryptRule);
            if (CollectionUtil.isEmpty(context.getDecryptFields())) {
                return;
            }
            decryptObject(parameterObject, context.getDecryptFields());
        }



    }

    /**
     * 判断集合第一个对象实例是否需要解密
     * @param parameterObject
     * @return
     * @throws Exception
     */
    protected boolean checkListFirstObject(Object parameterObject) throws Exception {
        //判断集合对象结果是否需要解密对象类型
        if(ClassUtil.isPrimitive(parameterObject.getClass())) {
            return false;
        }
        if(parameterObject == null) {
            return false;
        }

        FieldConditionContext context = new FieldConditionContext(parameterObject, decryptRule);
        return checkDecrypt(parameterObject, context);
    }

    /**
     * 根据数据解密配置 以及数据解密版本判断当前数据是否需解密
     * @param parameterObject
     * @return
     * @throws IllegalAccessException
     */
    protected boolean checkDecrypt(Object parameterObject, FieldConditionContext context) throws Exception {
        String className = parameterObject.getClass().getName();
        ResultMapConfiguration objectConfiguration = decryptRule.getDecryptRuleMap().get(className);
        //判断当前参数对象是否需加解密处理
        if(objectConfiguration == null || CollectionUtil.isEmpty(objectConfiguration.getPropertyMap())) {
            return checkChildObject(parameterObject, context);
        }
        return checkHistoryData(objectConfiguration, parameterObject);
    }

    /**
     * 历史数据兼容方案
     * @param objectConfiguration
     * @param parameterObject
     * @return
     * @throws IllegalAccessException
     */
    private boolean checkHistoryData(ResultMapConfiguration objectConfiguration, Object parameterObject) throws IllegalAccessException {
        //历史数据兼容方案:
        //判断当前对象是否需要解密数据,默认需解密
        if(objectConfiguration.getDecryptVersion() == null) {
            return true;
        }
        List<Field> classFieldList = ClassUtil.getAllClassFields(parameterObject.getClass());
        if(CollectionUtil.isEmpty(classFieldList)) {
            return false;
        }
        //无需解密时
        Optional<Field> decryptVersionOptionalField = classFieldList.stream().filter(field-> "decryptVersion".equalsIgnoreCase(field.getName())).collect(Collectors.toList()).stream().findFirst();
        if(!decryptVersionOptionalField.isPresent()) {
            return false;
        }

        try {
            Field decryptVersionField = decryptVersionOptionalField.get();
            decryptVersionField.setAccessible(true);
            Object originalValues = decryptVersionField.get(parameterObject);
            if (originalValues == null || !(originalValues instanceof Number)) {
                return false;
            }
            //判断当前对象数据是否需要解密
            return objectConfiguration.getDecryptVersion().equals(Integer.valueOf(String.valueOf(originalValues)));
        } catch (IllegalAccessException exception) {
            log.error("当前对象获取decryptVersion属性时发生异常: {}", exception.getMessage());
            throw exception;
        }
    }

    private boolean checkChildObject(Object parameterObject, FieldConditionContext context) throws Exception {
        List<Field> decryptFields = context.getDecryptFields();
        if (CollectionUtil.isEmpty(decryptFields)) {
            return false;
        }

        //处理所有解密属性
        for (Field field : decryptFields) {
            field.setAccessible(true);
            Object fieldValue = field.get(parameterObject);
            if (fieldValue == null) {
                continue;
            }
            //判断是否包含子对象, 不为jdk基本类型等
            if (!ClassUtil.isPrimitive(fieldValue.getClass())) {
                return true;
            }
        }
        return false;
    }

    protected void decryptObject(Object parameterObject, List<Field> decryptFields) throws Exception {
        //处理所有解密属性
        for (Field field : decryptFields) {
            Class filedClass = field.getType();

            //判断字段数据类型是否为非jdk包装类型&String类型&jdk基础数据类型
            if(ClassUtil.isPrimitive(filedClass)) {
                Optional<ResultConfiguration> resultConfigurationOptional = decryptRule.checkObjectValue(parameterObject.getClass().getName(), field.getName());
                if(resultConfigurationOptional == null) {
                    continue;
                }

                field.setAccessible(true);
                Object fieldValue = field.get(parameterObject);
                if(fieldValue == null) {
                    continue;
                }

                if (!(fieldValue instanceof String) && !(fieldValue instanceof Number)) {
                    continue;
                }
                //判断值是否为空
                if(StrUtil.isEmpty(String.valueOf(fieldValue))) {
                    continue;
                }

                if(log.isDebugEnabled()) {
                    log.debug("handlerDecrypt, objectName: {} , filedName: {} , originalValues: {}." , parameterObject.getClass().getName(), field.getName(), fieldValue);
                }

                //解密属性值
                Object encryptedValue = decryptRule.decryptObjectValue(resultConfigurationOptional, fieldValue);
                field.set(parameterObject, encryptedValue);
                continue;
            }


            if(!ClassUtil.isPrimitive(filedClass)) {
                field.setAccessible(true);
                Object fieldValue = field.get(parameterObject);
                if(fieldValue == null) {
                    continue;
                }
                log.info("字段数据类型非jdk包装类型&String类型&jdk基础数据类型, 进入递归匹配.");
                rewrite(fieldValue);
                continue;
            }


        }
    }

    public String getDecryptValue(String decryptValue) throws BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, UnsupportedEncodingException {
        return String.valueOf(decryptRule.getDecryptValue(null, decryptValue));
    }

    public String getDecryptValueOrNull(String decryptValue) {
        try {
            return getDecryptValue(decryptValue);
        } catch (Exception exception) {
            log.error("解密异常: {}.", exception.getMessage());
        }
        return null;
    }

    public String getDecryptValueOrDefault(String decryptValue) {
        try {
            return getDecryptValue(decryptValue);
        } catch (Exception exception) {
            log.error("解密异常: {}.", exception.getMessage());
        }
        return decryptValue;
    }


}
